package de.mmis.core.base.abstracttree.serializer;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import de.mmis.core.base.DeserializableAnnotations;
import de.mmis.core.base.DeserializableAnnotations.ConstructorDeserializable;
import de.mmis.core.base.DeserializableAnnotations.EnumDeserializable;
import de.mmis.core.base.MethodDescriptor;
import de.mmis.core.base.ObjectDescriptor;
import de.mmis.core.base.abstracttree.CStruct;
import de.mmis.core.base.abstracttree.InnerNode;
import de.mmis.core.base.abstracttree.LeafNode;
import de.mmis.core.base.abstracttree.LeafNode.Encoding;
import de.mmis.core.base.abstracttree.Tree;
import de.mmis.core.base.abstracttree.Tree.Type;
import de.mmis.core.base.abstracttree.serializer.internal.ClassSerializerMap;
import de.mmis.core.base.abstracttree.serializer.internal.ConstructorDeserializableSerializer;
import de.mmis.core.base.abstracttree.serializer.internal.EnumSerializer;
import de.mmis.core.base.abstracttree.serializer.selector.GenericAtomSelector;
import de.mmis.core.base.abstracttree.serializer.selector.GenericStructSelector;
import de.mmis.core.base.abstracttree.serializer.selector.IDSelector;
import de.mmis.core.base.abstracttree.serializer.selector.InstanceSelector;
import de.mmis.core.base.abstracttree.serializer.selector.InstanceSelector.InstanceSelectorException;
import de.mmis.core.base.abstracttree.serializer.selector.NumberSelector;
import de.mmis.core.base.abstracttree.serializer.selector.SimpleSelector;
import de.mmis.core.base.gclass.GClass;

public abstract class SerializerHelper {

	public static final String typeIdentifierPrefix = "$";

	/**
	 * deserializes a {@link Tree} to an Object
	 * 
	 * @param exp
	 *            {@link Tree} to deserialize
	 * @param toClass
	 *            object class to (try to) deserialize to
	 * @param classLoader
	 *            class loader which should be used to find resources and
	 *            classes if necessary.
	 * @param idmap
	 *            Map of all previously deserialized objects used for resolving
	 *            self-references
	 * @return deserialized object
	 * @throws DeserializableException
	 *             error while deserializing
	 */
	public static Object deserializeObject(Tree exp, GClass toClass,
			ClassLoader classLoader, Map<String, Object> idmap)
			throws DeserializableException {
		// start first phase
		InstanceSelector selector = deserializeObject(exp, classLoader, null,
				idmap);

		// start second phase
		try {
			return selector.createInstance(toClass);
		} catch (InstanceSelectorException e) {
			throw new DeserializableException(exp, e,
					"instance selection failed");
		}

	}

	/**
	 * first (of 2) deserialization phases. Without knowing the target type of
	 * the deserialization, the {@link Tree} will be converted to an
	 * intermediate representation, wrapped in an {@link InstanceSelector}
	 * implementation, which can be used to start the second phase later.
	 * 
	 * @param exp
	 *            {@link Tree} to deserialize
	 * @param classLoader
	 *            class loader which should be used to find resources and
	 *            classes if necessary.
	 * @param id
	 *            reference ID of this {@link Tree}, that should be added to the
	 *            idmap as soon as the final object is created
	 * @param idmap
	 *            Map of all previously deserialized objects used for resolving
	 *            self-references
	 * @return an implementation of the {@link InstanceSelector} class
	 *         representing the intermediate object which is the result of the
	 *         first phase
	 * @throws DeserializableException
	 *             error while deserializing
	 */
	private static InstanceSelector deserializeObject(Tree exp,
			ClassLoader classLoader, String id, Map<String, Object> idmap)
			throws DeserializableException {

		if (exp.getType() == Type.LEAF) {
			LeafNode atom = (LeafNode) exp;

			// String
			if (atom.getEncoding() == Encoding.Quoted)
				return new SimpleSelector(atom.getUTF8Data(), id, idmap);

			// byte array
			if (atom.getEncoding() != Encoding.Token)
				return new SimpleSelector(atom.getData().clone(), id, idmap);

			// null
			if (atom.getData().length == 0 || "null".equals(atom.getUTF8Data()))
				return new SimpleSelector(null, id, idmap);

			// label
			if (atom.getData()[0] == '@') {
				return new IDSelector(atom.getUTF8Data(), idmap);
			}

			// character
			if (atom.getData()[0] == '\'') {
				String string = atom.getUTF8Data();

				if (string.length() != 3 || string.charAt(0) != '\''
						|| string.charAt(2) != '\'')
					throw new DeserializableException(exp,
							"malformed UTF-8 encoded character");

				return new SimpleSelector(new Character(string.charAt(1)), id,
						idmap);

			}

			// boolean
			if ("true".equals(atom.getUTF8Data()))
				return new SimpleSelector(new Boolean(true), id, idmap);
			if ("false".equals(atom.getUTF8Data()))
				return new SimpleSelector(new Boolean(false), id, idmap);

			// numbers
			if (atom.getUTF8Data().equals("Inf"))
				return new NumberSelector(atom, new Double(
						Double.POSITIVE_INFINITY), null, id, idmap);
			if (atom.getUTF8Data().equals("-Inf"))
				return new NumberSelector(atom, new Double(
						Double.NEGATIVE_INFINITY), null, id, idmap);
			if (atom.getUTF8Data().equals("NaN"))
				return new NumberSelector(atom, new Double(Double.NaN), null,
						id, idmap);
			if ((atom.getData()[0] >= '0' && atom.getData()[0] <= '9')
					|| atom.getData()[0] == '.' || atom.getData()[0] == '-')
				return deserializeNumber(atom, id, idmap);

			// default: generic enums or byte arrays
			return new GenericAtomSelector(atom, classLoader, id, idmap);

		} else if (exp.getType() == Type.INNER) {
			InnerNode struct = (InnerNode) exp;

			if (struct.getNumberOfSubTrees() > 0) {
				if (struct.getSubTree(0).getType() == Type.LEAF) {
					LeafNode atom = (LeafNode) struct.getSubTree(0);
					if (atom.getData().length > 0 && atom.getData()[0] == '@') {
						String refID = atom.getUTF8Data();

						if (!idmap.containsKey(refID)) {
							id = refID;
							if (struct.getNumberOfSubTrees() == 2)
								return deserializeObject(struct.getSubTree(1),
										classLoader, id, idmap);

							throw new DeserializableException(struct,
									"label needs exactly one additional Tree");
						}
					}

					if (atom.getData().length > typeIdentifierPrefix.length()
							&& atom.getUTF8Data().startsWith(
									typeIdentifierPrefix)) {
						Tree[] exps = new Tree[struct.getNumberOfSubTrees() - 1];
						for (int i = 0; i < exps.length; i++)
							exps[i] = struct.getSubTree(i + 1);

						return deserializeSpecialClass(exps, atom.getUTF8Data()
								.substring(typeIdentifierPrefix.length()),
								classLoader, id, idmap);
					}
				}
			}

			return new GenericStructSelector(struct, classLoader, id, idmap);
		}

		throw new DeserializableException(exp, "unknown type of Tree");
	}

	/**
	 * auxiliary method of first deserialization phase. Given a type identifier
	 * the method finds the deserializing class and runs the corresponding
	 * deserialization method.
	 * 
	 * @param exps
	 *            the array of {@link Tree}s passed to the deserialization
	 *            method.
	 * @param typeIdentifier
	 *            type identifier which determines the deserialization class
	 *            that should be used.
	 * @param classLoader
	 *            class loader which should be used to find resources and
	 *            classes if necessary.
	 * @param id
	 *            reference ID of this {@link Tree}, that should be added to the
	 *            idmap as soon as the final object is created
	 * @param idmap
	 *            Map of all previously deserialized objects used for resolving
	 *            self-references
	 * @return an implementation of the {@link InstanceSelector} class
	 *         representing the intermediate object which is the result of the
	 *         first phase
	 * @throws DeserializableException
	 *             error while deserializing
	 */
	private static InstanceSelector deserializeSpecialClass(Tree[] exps,
			String typeIdentifier, ClassLoader classLoader, String id,
			Map<String, Object> idmap) throws DeserializableException {

		// get property file resource containing the name of the class that
		// should be used
		InputStream instream = classLoader
				.getResourceAsStream("de/mmis/core/base/abstracttree/deserializer/"
						+ typeIdentifier);
		if (instream == null)
			throw new DeserializableException(new InnerNode(exps),
					"cannot find suiting deserializer for type \""
							+ typeIdentifier + "\": property file not found");

		// read properties file
		Properties p = new Properties();
		try {
			p.load(instream);
		} catch (IOException e) {
			throw new DeserializableException(new InnerNode(exps), e,
					"cannot find suiting deserializer for type \""
							+ typeIdentifier
							+ "\": error while reading property file");
		}

		// get (any) class name in this property map
		Set<String> keys = p.stringPropertyNames();
		if (keys.isEmpty())
			throw new DeserializableException(new InnerNode(exps),
					"cannot find suiting deserializer for type \""
							+ typeIdentifier + "\": no keys in property file");
		String classname = keys.iterator().next();

		// load class from class name
		Class<?> clazz = null;
		try {
			clazz = Class.forName(classname, false, classLoader);
		} catch (ClassNotFoundException e) {
			throw new DeserializableException(new InnerNode(exps), e,
					"cannot find suiting deserializer for type \""
							+ typeIdentifier + "\": class \"" + classname
							+ "\" cannot be loaded");
		}

		// use loaded class as deserialization class
		return deserializeWithClass(clazz, exps, classLoader, id, idmap);
	}

	/**
	 * 
	 * auxiliary method of first deserialization phase. Given a deserialization
	 * class the method finds correct dezerialization method and runs it.
	 * 
	 * @param clazz
	 *            class object which determines the deserialization class that
	 *            should be used.
	 * @param exps
	 *            the array of {@link Tree}s passed to the deserialization
	 *            method.
	 * @param classLoader
	 *            class loader which should be used to find resources and
	 *            classes if necessary.
	 * @param id
	 *            reference ID of this {@link Tree}, that should be added to the
	 *            idmap as soon as the final object is created
	 * @param idmap
	 *            Map of all previously deserialized objects used for resolving
	 *            self-references
	 * @return an implementation of the {@link InstanceSelector} class
	 *         representing the intermediate object which is the result of the
	 *         first phase
	 * @throws DeserializableException
	 *             error while deserializing
	 */
	public static InstanceSelector deserializeWithClass(Class<?> clazz,
			Tree[] exps, ClassLoader classLoader, String id,
			Map<String, Object> idmap) throws DeserializableException {

		// if deserialization class is ConstructorDeserializable, use the
		// corresponding method
		if (clazz.getAnnotation(ConstructorDeserializable.class) != null) {
			return ConstructorDeserializableSerializer.deserialize(exps,
					classLoader, id, idmap, clazz);
		}

		// if deserialization class is EnumDeserializable, find the enum value
		// and use the
		// corresponding method
		if (clazz.getAnnotation(EnumDeserializable.class) != null) {
			if (exps.length != 1)
				throw new DeserializableException(new InnerNode(exps),
						"enum Trees must have exactly one parameter");
			if (exps[0].getType() != Type.LEAF)
				throw new DeserializableException(new InnerNode(exps),
						"enum constant name needs to be an atom");
			return new SimpleSelector(EnumSerializer.getEnumConstant(
					(LeafNode) exps[0], clazz), id, idmap);
		}

		// try to find custom deserialization method
		for (Method m : clazz.getDeclaredMethods()) {
			if (m.getAnnotation(DeserializableAnnotations.DeserializationMethod.class) != null) {
				if (Modifier.isPublic(m.getModifiers())
						&& Modifier.isStatic(m.getModifiers()))
					try {
						Object ret = null;
						if (m.getParameterTypes().length == 4
								&& m.getParameterTypes()[0] == Tree[].class
								&& m.getParameterTypes()[1] == ClassLoader.class
								&& m.getParameterTypes()[2] == String.class
								&& m.getParameterTypes()[3] == Map.class)
							ret = m.invoke(null, exps, classLoader, id, idmap);
						else
							continue;
						if (ret instanceof InstanceSelector)
							return (InstanceSelector) ret;
						return new SimpleSelector(ret, id, idmap);
					} catch (IllegalArgumentException e) {
						// should not happen
						throw new DeserializableException(new InnerNode(exps),
								e);
					} catch (IllegalAccessException e) {
						// should not happen
						throw new DeserializableException(new InnerNode(exps),
								e);
					} catch (InvocationTargetException e) {
						if (e.getCause() instanceof DeserializableException)
							throw (DeserializableException) e.getCause();

						throw new DeserializableException(new InnerNode(exps),
								e);
					}
			}
		}

		// if class is subclass of CStruct, it should be deserialized as a
		// CStruct
		if (CStruct.class.isAssignableFrom(clazz)) {
			try {
				CStruct c = (CStruct) clazz.getConstructor().newInstance();
				return CStruct.deserializeProperties(c, exps, classLoader, id,
						idmap);
			} catch (IllegalArgumentException e) {
				throw new DeserializableException(new InnerNode(exps), e,
						"error invoking CStruct constructor");
			} catch (SecurityException e) {
				throw new DeserializableException(new InnerNode(exps), e,
						"error invoking CStruct constructor");
			} catch (InstantiationException e) {
				throw new DeserializableException(new InnerNode(exps), e,
						"error invoking CStruct constructor");
			} catch (IllegalAccessException e) {
				throw new DeserializableException(new InnerNode(exps), e,
						"error invoking CStruct constructor");
			} catch (InvocationTargetException e) {
				throw new DeserializableException(new InnerNode(exps), e,
						"error invoking CStruct constructor");
			} catch (NoSuchMethodException e) {
				throw new DeserializableException(new InnerNode(exps), e,
						"error invoking CStruct constructor");
			}
		}

		// no deserialization method found
		throw new DeserializableException(
				new InnerNode(exps),
				"cannot find suiting deserializer for type: class \""
						+ clazz
						+ "\" has no properly annotated and formed deserialization method");
	}

	private static String allowedCharacters = "0123456789";

	/**
	 * 
	 * auxiliary method of first deserialization phase. Given an atomic
	 * {@link Tree} containing a number the method returns a instance selector
	 * representing the number.
	 * 
	 * @param atom
	 *            atomic {@link Tree} containing the number
	 * @param id
	 *            reference ID of this {@link Tree}, that should be added to the
	 *            idmap as soon as the final object is created
	 * @param idmap
	 *            Map of all previously deserialized objects used for resolving
	 *            self-references
	 * @return an implementation of the {@link InstanceSelector} class
	 *         representing the intermediate object which is the result of the
	 *         first phase
	 * @throws DeserializableException
	 *             error while deserializing
	 */
	private static InstanceSelector deserializeNumber(LeafNode atom, String id,
			Map<String, Object> idmap) throws DeserializableException {

		NumberFormat numberFormat = getNumberFormat();

		boolean separator = false;

		String data = atom.getUTF8Data();

		for (int i = 0; i < data.length(); i++) {
			if (allowedCharacters.indexOf(data.charAt(i)) < 0) {
				if (i == 0 && data.charAt(i) == '-')
					continue;
				if (i == data.length() - 1)
					continue;
				if (!separator && data.charAt(i) == '.')
					separator = true;
				else
					throw new DeserializableException(atom,
							"illegal character in number at position " + i);
			}
		}

		try {
			Number number = numberFormat.parse(data);
			Character modifier = null;
			if (allowedCharacters.indexOf(data.charAt(data.length() - 1)) < 0
					&& data.charAt(data.length() - 1) != '.')
				modifier = new Character(data.charAt(data.length() - 1));

			return new NumberSelector(atom, number, modifier, id, idmap);
		} catch (ParseException e) {
			throw new DeserializableException(atom, e,
					"error while parsing number");
		}
	}

	/**
	 * auxiliary method creating a new {@link NumberFormat} object. This is
	 * necessary as the class {@link NumberFormat} is not thread safe.
	 * 
	 * @return a newly created {@link NumberFormat} object.
	 */
	public static NumberFormat getNumberFormat() {
		NumberFormat numberFormat = NumberFormat.getInstance(Locale.ENGLISH);
		numberFormat.setGroupingUsed(false);
		numberFormat.setMaximumFractionDigits(Integer.MAX_VALUE);
		return numberFormat;
	}

	/**
	 * auxiliary class representing an Object returned by an invoked method.
	 * This kind of wrapping is used to preserve serialization information
	 * provided in the used method call
	 * 
	 * @author Christian Koch
	 */
	public static class MethodReturnValue {
		private Object object;
		private boolean isVoid;
		private ClassLoader classLoader;

		public Object getObject() {
			return this.object;
		}

		public MethodReturnValue(Object object, boolean isVoid,
				ClassLoader classLoader) {
			this.object = object;
			this.isVoid = isVoid;
			this.classLoader = classLoader;
		}

		public Tree toTree(boolean explicit) {
			if (isVoid)
				return null;

			return SerializerHelper.serializeObject(this.object, explicit,
					new IdentityHashMap<Object, String>(), classLoader);
		}
	}

	/**
	 * Exception thrown if an error occurred while invoking an object method
	 * 
	 * @author Christian Koch
	 */
	public static class MethodInvocationException extends Exception {
		public MethodInvocationException(String message) {
			super(message);
		}

		public MethodInvocationException(String message, Throwable cause) {
			super(message, cause);
		}

		/**
		 * @return {@link Tree} representation of this exception
		 */
		public Tree toTree(boolean explicit, Map<Object, String> refmap,
				ClassLoader classLoader) {
			if (refmap == null)
				refmap = new IdentityHashMap<Object, String>();
			if (this.getCause() != null
					&& this.getCause() instanceof InvocationTargetException) {
				InvocationTargetException e = (InvocationTargetException) this
						.getCause();
				InnerNode errormsg = new InnerNode(new LeafNode(":error"),
						new LeafNode(e.getCause().getClass().getName()),
						Tree.fromObject(e.getCause().getMessage(), explicit,
								refmap, classLoader),
						SerializerHelper.serializeObject(e.getCause(),
								explicit, refmap,
								MethodInvocationException.class
										.getClassLoader()));
				return errormsg;
			}

			return new InnerNode(new LeafNode(":error", Encoding.Token),
					new LeafNode(this.getMessage(), Encoding.Quoted));
		}

		private static final long serialVersionUID = -6453148042515598559L;
	}

	/**
	 * custom {@link Exception} class thrown when a no matching method can be
	 * found to a given {@link Tree}
	 * 
	 * @author Christian Koch
	 * 
	 */
	public static class NoMatchingMethodFoundException extends Exception {
		private static final long serialVersionUID = -5026723035886914686L;

		private MethodNotMatchingException[] exceptions;

		public NoMatchingMethodFoundException(
				List<MethodNotMatchingException> exlist) {

			super("all tried methods do not match the Tree");

			this.exceptions = new MethodNotMatchingException[exlist.size()];
			for (int i = 0; i < exlist.size(); i++)
				this.exceptions[i] = exlist.get(i);
		}

		public NoMatchingMethodFoundException(String message) {
			super(message);
			this.exceptions = null;
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(super.toString());
			if (exceptions != null) {
				sb.append("\n inner exception list:\n");

				for (MethodNotMatchingException e : exceptions) {
					StringWriter sw = new StringWriter();
					PrintWriter pw = new PrintWriter(sw);
					e.printStackTrace(pw);
					pw.flush();
					sb.append(sw.toString());
					sb.append("\n");
				}
			}
			return sb.toString();
		}

		/**
		 * @return inner exceptions thrown when trying the different possible
		 *         methods
		 */
		public MethodNotMatchingException[] getDeserialisationExceptions() {
			return this.exceptions;
		}

		/**
		 * @return {@link Tree} representation of this exception
		 */
		public Tree toTree(boolean explicit, Map<Object, String> refmap,
				ClassLoader classLoader) {
			if (refmap == null)
				refmap = new IdentityHashMap<Object, String>();
			return new InnerNode(new LeafNode(":error"), new LeafNode(
					NoMatchingMethodFoundException.class.getName()),
					new LeafNode(this.getMessage() == null ? "" : this
							.getMessage(), Encoding.Quoted),
					SerializerHelper.serializeObject(this, explicit, refmap,
							classLoader));
		}
	}

	/**
	 * custom {@link Exception} class thrown when a specific method does not
	 * match a given {@link Tree}
	 * 
	 * @author Christian Koch
	 * 
	 */
	public static class MethodNotMatchingException extends Exception {
		public MethodNotMatchingException(String message, Throwable cause) {
			super(message, cause);
		}

		private static final long serialVersionUID = -3920215282057460532L;
	}

	/**
	 * answer method call represented by given {@link Tree}
	 * 
	 * @param objectDescriptor
	 *            object to invoke method on
	 * @param methodCall
	 *            {@link Tree} representing method call
	 * @param classLoader
	 * @param idmap
	 *            Map of all previously deserialized objects used for resolving
	 *            self-references
	 * @return the result of the method invocation
	 * @throws MethodInvocationException
	 *             invalid {@link Tree} or an error occurred while invoking the
	 *             method
	 * @throws NoMatchingMethodFoundException
	 *             no method matching given Tree
	 */
	public static MethodReturnValue invokeObjectMethod(
			ObjectDescriptor objectDescriptor, Tree methodCall,
			ClassLoader classLoader, Map<String, Object> idmap)
			throws MethodInvocationException, NoMatchingMethodFoundException {
		GClass forceclass = null;
		Tree expToProcess = methodCall;

		ArrayList<MethodNotMatchingException> exlist = new ArrayList<MethodNotMatchingException>();

		for (MethodDescriptor methodDescriptor : objectDescriptor
				.getMethodDescriptors()) {
			MethodReturnValue mrv;
			try {
				mrv = invokeObjectMethod(methodDescriptor, expToProcess,
						forceclass, classLoader, idmap);
			} catch (MethodNotMatchingException e) {
				mrv = null;
				exlist.add(e);
			}
			if (mrv != null)
				return mrv;
		}
		if (exlist.isEmpty())
			throw new NoMatchingMethodFoundException("no method matching \""
					+ methodCall + "\" found");

		throw new NoMatchingMethodFoundException(exlist);
	}

	/**
	 * try to answer method call represented by given {@link Tree} using given
	 * object method
	 * 
	 * @param methodDescriptor
	 *            descriptor of method to be used
	 * @param exp
	 *            {@link Tree} representing method call
	 * @param forcedClass
	 *            when not null, determines the Class as which the return value
	 *            should be serialized later
	 * @param classLoader
	 * @param idmap
	 *            Map of all previously deserialized objects used for resolving
	 *            self-references
	 * @return the result of the method invocation, or null, if the given
	 *         {@link Tree} was not applicable to the given method
	 * @throws MethodInvocationException
	 *             an error occurred while invoking the method
	 * @throws MethodNotMatchingException
	 *             parameters not matching method parameters
	 */
	public static MethodReturnValue invokeObjectMethod(
			MethodDescriptor methodDescriptor, Tree exp, GClass forcedClass,
			ClassLoader classLoader, Map<String, Object> idmap)
			throws MethodInvocationException, MethodNotMatchingException {
		String methodName;
		if (exp.getType() == Type.LEAF) {
			methodName = ((LeafNode) exp).getUTF8Data();
			if (!methodName.equals(methodDescriptor.getName()))
				return null; // name not matching

			if (methodDescriptor.getArgumentCount() != 0)
				throw new MethodNotMatchingException(
						"number of parameters not matching in "
								+ methodDescriptor.toString(), null);
		} else {
			// check structure of Tree
			if (exp.getType() != Type.INNER)
				throw new MethodInvocationException(
						"Tree neither Atom nor Struct");
			if (((InnerNode) exp).getNumberOfSubTrees() == 0)
				throw new MethodInvocationException("Tree Struct is empty");
			if (((InnerNode) exp).getSubTree(0).getType() != Type.LEAF)
				throw new MethodInvocationException(
						"method name is not an Atom");

			// check method name
			methodName = ((LeafNode) ((InnerNode) exp).getSubTree(0))
					.getUTF8Data();
			if (methodName.startsWith("!"))
				methodName = "set" + methodName.substring(1);
			if (!methodName.equals(methodDescriptor.getName()))
				return null; // name not matching

			if (((InnerNode) exp).getNumberOfSubTrees() != methodDescriptor
					.getArgumentCount() + 1)
				throw new MethodNotMatchingException(
						"number of parameters not matching in "
								+ methodDescriptor.toString(), null);
		}

		// deserialize parameters
		Object[] arguments = new Object[methodDescriptor.getArgumentCount()];
		for (int i = 0; i < methodDescriptor.getArgumentCount(); i++) {
			GClass paraType = methodDescriptor.getParameterType(i);
			try {
				arguments[i] = SerializerHelper.deserializeObject(
						((InnerNode) exp).getSubTree(i + 1), paraType,
						classLoader, idmap);
			} catch (DeserializableException e1) {
				throw new MethodNotMatchingException("parameter " + (i + 1)
						+ " of " + methodDescriptor.toString(), e1);
				// System.err.println(e1.getMessage()); // debug
				// return null;
			}
		}

		// now we can invoke the method
		Object result;
		try {
			result = methodDescriptor.invoke(arguments);
		} catch (IllegalArgumentException e) {
			throw new MethodInvocationException(
					"IllegalArgumentException while invoking method", e);
		} catch (IllegalAccessException e) {
			throw new MethodInvocationException(
					"IllegalAccessException while invoking method", e);
		} catch (InvocationTargetException e) {
			throw new MethodInvocationException(
					"InvocationTargetException while invoking method", e);
		}

		// create the output
		return new MethodReturnValue(result, methodDescriptor.getReturnType()
				.equalsClass(void.class), classLoader);
	}

	/**
	 * serializes an object into a {@link Tree}
	 * 
	 * @param object
	 *            object to serialize
	 * @return {@link Tree} representing given object
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static Tree serializeObject(Object object, boolean explicit,
			Map<Object, String> refmap, ClassLoader classLoader) {
		if (object == null)
			return new LeafNode("null");

		// check if object already serialized
		if (refmap.containsKey(object)) {
			String i = refmap.get(object);

			if (i == null) {
				int tmp = 1;
				while (refmap.containsValue(new String("@" + tmp)))
					tmp++;
				i = "@" + tmp;
				refmap.put(object, new String(i));
			}
			return new LeafNode(i);
		}

		refmap.put(object, null);

		// serialize object
		Tree ret;
		if (object instanceof Tree && !explicit)
			ret = (Tree) object;
		else {
			Serializer ser = ClassSerializerMap.getSerialiser(
					object.getClass(), classLoader);
			ret = ser.serialize(object, explicit, refmap, classLoader);
		}

		// wrap reference ID around serialized object if necessary
		String i = refmap.get(object);
		if (i != null)
			ret = new InnerNode(new LeafNode(i), ret);
		else
			refmap.remove(object);

		return ret;
	}
}
